﻿using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GTI = Gadgeteer.Interfaces;
using System;
using System.Threading;

namespace Gadgeteer.Modules.GHIElectronics
{
    // -- CHANGE FOR MICRO FRAMEWORK 4.2 --
    // If you want to use Serial, SPI, or DaisyLink (which includes GTI.SoftwareI2C), you must do a few more steps
    // since these have been moved to separate assemblies for NETMF 4.2 (to reduce the minimum memory footprint of Gadgeteer)
    // 1) add a reference to the assembly (named Gadgeteer.[interfacename])
    // 2) in GadgeteerHardware.xml, uncomment the lines under <Assemblies> so that end user apps using this module also add a reference.

    /// <summary>
    /// A simple joystick module with button for .NET Gadgeteer.
    /// </summary>
    public class Joystick : GTM.Module
    {
        private GTI.AnalogInput inputX;
        private GTI.AnalogInput inputY;
        private GTI.InterruptInput input;
		private double offsetX;
		private double offsetY;

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socketNumber">The mainboard socket that has the module plugged into it.</param>
        public Joystick(int socketNumber)
        {
            Socket socket = Socket.GetSocket(socketNumber, true, this, null);
            this.inputX = new GTI.AnalogInput(socket, Socket.Pin.Four, this);
            this.inputY = new GTI.AnalogInput(socket, Socket.Pin.Five, this);
            this.input = new GTI.InterruptInput(socket, GT.Socket.Pin.Three, GTI.GlitchFilterMode.On, GTI.ResistorMode.PullUp, GTI.InterruptMode.RisingAndFallingEdge, this);
            this.input.Interrupt += new GTI.InterruptInput.InterruptEventHandler(this._input_Interrupt);
			this.offsetX = 0;
			this.offsetY = 0;
        }

        private void _input_Interrupt(GTI.InterruptInput input, bool value)
        {
            JoystickState joystickState = value ? JoystickState.Released : JoystickState.Pressed;

            this.OnJoystickEvent(this, joystickState);
        }

        /// <summary>
        /// Gets a value that indicates whether the state of this Joystick is Pressed.
        /// </summary>
        public bool IsPressed
        {
            get
            {
                return !this.input.Read();
            }
        }

        /// <summary>
        /// Represents the state of the <see cref="Joystick"/> object.
        /// </summary>
        public enum JoystickState
        {
            /// <summary>
            /// The state of Joystick is Pressed.
            /// </summary>
            Pressed = 0,
            /// <summary>
            /// The state of Joystick is Released.
            /// </summary>
            Released = 1
        }

        /// <summary>
        /// Represents the delegate that is used to handle the <see cref="JoystickReleased"/>
        /// and <see cref="JoystickPressed"/> events.
        /// </summary>
        /// <param name="sender">The <see cref="Joystick"/> object that raised the event.</param>
        /// <param name="state">The state of the Joystick.</param>
        public delegate void JoystickEventHandler(Joystick sender, JoystickState state);

        /// <summary>
        /// Raised when the state of <see cref="Joystick"/> is Released.
        /// </summary>
        /// <remarks>
        /// Implement this event handler and the <see cref="JoystickPressed"/> event handler
        /// when you want to provide an action associated with Joystick activity.
        /// The state of the Joystick is passed to the <see cref="JoystickEventHandler"/> delegate,
        /// so you can use the same event handler for both Joystick states.
        /// </remarks>
        public event JoystickEventHandler JoystickReleased;

        /// <summary>
        /// Raised when the state of <see cref="Joystick"/> is Pressed.
        /// </summary>
        /// <remarks>
        /// Implement this event handler and the <see cref="JoystickReleased"/> event handler
        /// when you want to provide an action associated with Joystick activity.
        /// Since the state of the Joystick is passed to the <see cref="JoystickEventHandler"/> delegate,
        /// you can use the same event handler for both Joystick states.
        /// </remarks>
        public event JoystickEventHandler JoystickPressed;

        private JoystickEventHandler onJoystick;

        /// <summary>
        /// Raises the <see cref="JoystickReleased"/> or <see cref="JoystickPressed"/> event.
        /// </summary>
        /// <param name="sender">The <see cref="Joystick"/> that raised the event.</param>
        /// <param name="JoystickState">The state of the Joystick.</param>
        protected virtual void OnJoystickEvent(Joystick sender, JoystickState JoystickState)
        {
            if (this.onJoystick == null)
            {
                this.onJoystick = new JoystickEventHandler(this.OnJoystickEvent);
            }

            if (Program.CheckAndInvoke((JoystickState == JoystickState.Released ? this.JoystickReleased : this.JoystickPressed), this.onJoystick, sender, JoystickState))
            {
                switch (JoystickState)
                {
                    case JoystickState.Released:
                        this.JoystickReleased(sender, JoystickState);
                        break;
                    case JoystickState.Pressed:
                        this.JoystickPressed(sender, JoystickState);
                        break;
                }

            }
        }

        /// <summary>
        /// Gets the joystick position.
        /// </summary>
        /// <returns></returns>
        public Position GetPosition()
        {
			double x = this.Read(this.inputX);
			double y = this.Read(this.inputY);

            Position pos = new Position()
            {
                X = x * 2 - 1 - this.offsetX,
                Y = (1 - y) * 2 - 1 - this.offsetY
            };

            return pos;
        }

		/// <summary>
		/// Gets the joystick position. Use GetPosition() instead.
		/// </summary>
		/// <returns></returns>
		[Obsolete] public Position GetJoystickPosition()
		{
			return this.GetPosition();
		}

		/// <summary>
		/// Calibrates the joystick such that the current position is interpreted as 0.
		/// </summary>
		public void Calibrate()
		{
			this.offsetX = this.Read(this.inputX) * 2 - 1;
			this.offsetY = (1 - this.Read(this.inputY)) * 2 - 1;
		}

        /// <summary>
        /// Structure that contains the X and Y position of the joystick from -1.0 to 1.0 (0.0 means centered).
        /// </summary>
        public struct Position
        {
            /// <summary>
            /// The X coordinate of the joystick from -1.0 to 1.0 (0.0 means centered).
            /// </summary>
            public double X;

            /// <summary>
            /// The Y coordinate of the joystick from -1.0 to 1.0 (0.0 means centered).
            /// </summary>
            public double Y;
        }

		private double Read(GTI.AnalogInput input)
		{
			double total = 0;

			for (int i = 0; i < 10; i++)
				total += input.ReadProportion();

			return total / 10;
		}
    }
}